#include <sys/select.h>
#include <stdlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <string.h>
#include "XWin.h"
#include "i18n.h"

using namespace std;

unsigned char *repack64(unsigned char *repack_data, long repack_sz);

XWin::XWin(int xx, int yy, int ww, int hh)
    : eventMask(NoEventMask), x(xx), y(yy), w(ww), h(hh) {

  /* if(threads) XInitThreads() */
  int defScreen;
  XSetWindowAttributes attr;
  attr.bit_gravity = StaticGravity;
  attr.backing_store = Always;

  if (!(display = XOpenDisplay(NULL))) {
    throw _("Couldn't open display");
  }

  defScreen = DefaultScreen(display);

  if (!(visual = DefaultVisual(display, defScreen))) {
    throw _("Couldn't get visual");
  }

  if (!(colormap = DefaultColormap(display, defScreen))) {
    throw _("Couln't get colormap");
  }

  if (!(depth = DefaultDepth(display, defScreen))) {
    throw _("Couldn't get depth");
  }

  if (!(window = XCreateSimpleWindow(display, DefaultRootWindow(display), x, y,
                                     w, h, 0, 0, 0))) {
    throw _("Couldn't create window");
  }

  /* prepare for delete window */
  delWindow = XInternAtom(display, "WM_DELETE_WINDOW", false);
  /* Set WM Protocols to report window delete event */
  XSetWMProtocols(display, window, &delWindow, 1);

  XClassHint ch = {(char *)PACKAGE_NAME, (char *)PACKAGE_NAME };
  XSetClassHint(display, window, &ch);
  XChangeWindowAttributes(display, window, CWBitGravity | CWBackingStore,
                          &attr);
}

XWin::~XWin() {
  XDestroyWindow(display, window);
  XCloseDisplay(display);
}

void XWin::selectInput(int ev_mask, int taskbar_set) {
  eventMask = ev_mask;
  XSelectInput(display, window, ev_mask);

  if (taskbar_set) {
    XSelectInput(display, XDefaultRootWindow(display), PropertyChangeMask);
    eventMask = ev_mask | PropertyChangeMask;
  }
}

void XWin::lowerWindow() { XLowerWindow(display, window); }

void XWin::raiseWindow() { XRaiseWindow(display, window); }

void XWin::mapWindow() { XMapWindow(display, window); }

void XWin::unMapWindow() { XUnmapWindow(display, window); }

bool XWin::nextEvent(XEvent *ev) {

  int qlen;

  do {
    /* queue length is relate to my only window */
    if ((qlen = XQLength(display))) {
      if (XCheckTypedEvent(display, ClientMessage, ev) == True) {
        if ((Atom) ev->xclient.data.l[0] == delWindow) {
          exit(0);
        } else {
          continue;
        }
      }

      if (XCheckMaskEvent(display, eventMask, ev) == False) {
        /* process events we're not waiting for */
        XSync(display, True);
        continue;
      }
    } else {
      XMaskEvent(display, eventMask, ev);
    }
  } while ((ev->type == MotionNotify || ev->type == PropertyNotify ||
            ev->type == ClientMessage) && qlen > 0);

  return true;
}

void XWin::setName(char *name) {
  XTextProperty windowName;
  XStringListToTextProperty(&name, 1, &windowName);

  XSetWMName(display, window, &windowName);
}

void XWin::moveNresize(int x, int y, int w, int h) {
  XMoveResizeWindow(display, window, x, y, w, h);
}

int XWin::screenWidth() const {
  return WidthOfScreen(DefaultScreenOfDisplay(display));
}

int XWin::screenHeight() const {
  return HeightOfScreen(DefaultScreenOfDisplay(display));
}

Display *XWin::getDisplay() { return display; }
Visual *XWin::getVisual() { return visual; }
Colormap XWin::getColormap() { return colormap; }
Drawable XWin::getDrawable() { return window; }

/* Dont allow window manager to decorate / restack / remap / ... */
void XWin::setOverrideRedirection(Bool ovr) {
  XSetWindowAttributes attr;

  attr.override_redirect = ovr;
  attr.background_pixmap = None; //Copy from back;

  XChangeWindowAttributes(display, window, CWOverrideRedirect | CWBackPixmap,
                          &attr);
}

void XWin::setDockWindow() {
  Atom a = XInternAtom(display, "_NET_WM_WINDOW_TYPE", True);

  if (a != None) {
    //Atom prop = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DESKTOP", True);
    //Atom prop = XInternAtom(display, "_NET_WM_WINDOW_TYPE_NORMAL", True);
    Atom prop = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DOCK", True);
    XChangeProperty(display, window, a, XA_ATOM, 32, PropModeReplace,
                    (unsigned char *)&prop, 1);
  }
}

void XWin::noDecorations() {
  Atom a = XInternAtom(display, "_MOTIF_WM_HINTS", True);

  if (a != None) {
    long prop[5] = { 2, 0, 0, 0, 0 };
    XChangeProperty(display, window, a, a, 32, PropModeReplace,
                    (unsigned char *)prop, 5);
  }
}

void XWin::setSticky() {
  Atom a = XInternAtom(display, "_NET_WM_DESKTOP", True);

  if (a != None) {
    long prop = 0xFFFFFFFF;
    XChangeProperty(display, window, a, XA_CARDINAL, 32, PropModeAppend,
                    (unsigned char *)&prop, 1);
  }

  a = XInternAtom(display, "_NET_WM_STATE", True);

  if (a != None) {
    Atom prop = XInternAtom(display, "_NET_WM_STATE_STICKY", True);
    XChangeProperty(display, window, a, XA_ATOM, 32, PropModeAppend,
                    (unsigned char *)&prop, 1);
  }
}

void XWin::skipTaskNPager() {

  Atom a = XInternAtom(display, "_NET_WM_STATE", True);

  if (a != None) {
    Atom prop = XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", True);
    XChangeProperty(display, window, a, XA_ATOM, 32, PropModeAppend,
                    (unsigned char *)&prop, 1);
  }

  a = XInternAtom(display, "_NET_WM_STATE", True);

  if (a != None) {
    Atom prop = XInternAtom(display, "_NET_WM_STATE_SKIP_PAGER", True);
    XChangeProperty(display, window, a, XA_ATOM, 32, PropModeAppend,
                    (unsigned char *)&prop, 1);
  }
}

void XWin::bottomLayer() {
  Atom a = XInternAtom(display, "_WIN_LAYER", True);

  if (a != None) {
    long prop = 0; // 6 is above && _NET_WM_STATE_ABOVE in STATE
    XChangeProperty(display, window, a, XA_CARDINAL, 32, PropModeAppend,
                    (unsigned char *)&prop, 1);
  }

  a = XInternAtom(display, "_NET_WM_STATE", True);

  if (a != None) {
    Atom prop = XInternAtom(display, "_NET_WM_STATE_BELOW", True);
    XChangeProperty(display, window, a, XA_ATOM, 32, PropModeAppend,
                    (unsigned char *)&prop, 1);
  }
}

char *XWin::atomName(Atom atomname) { return XGetAtomName(display, atomname); }

unsigned char *XWin::windowProp(Window *targetWin, const char *reqProp,
                                unsigned long *len) {
  Atom a = XInternAtom(display, reqProp, True), actual_type;
  Window tWin;
  Atom proptype;
  const char *workAreaProp = "_NET_WORKAREA";
  const char *viewportProp = "_NET_DESKTOP_VIEWPORT";

  //the following switch would better be organized in some "protos.h" later
  if (targetWin == NULL) {
    tWin = XDefaultRootWindow(display);

    if ((reqProp == workAreaProp) || (reqProp == viewportProp)) {
      proptype = XA_CARDINAL;
    } else {
      proptype = XA_WINDOW;
    }
  } else {
    tWin = *targetWin;
    proptype = XA_STRING;
  }

  if (a != None) {
    int form;
    unsigned long remain;
    unsigned char *list;

    if (XGetWindowProperty(display, tWin, a, 0, 1024, False, proptype,
                           &actual_type, &form, len, &remain, &list) !=
        Success) {
      return 0;
    }

    return list;
  }

  return 0;
}

Atom XWin::issetHint(Window targetWin, const char *reqProp,
                     const char *reqHint) {
  Atom a = XInternAtom(display, reqProp, True), actual_type;

  if (a != None) {
    int form;
    unsigned long remain;
    unsigned char *list;
    unsigned long len;

    //	If not disabled, XErrorHandler will interrupt wbar
    if (XGetWindowProperty(display, targetWin, a, 0, 1024, False, XA_ATOM,
                           &actual_type, &form, &len, &remain, &list) !=
        Success) {
      return 0;
    }

    Atom *array = (Atom *)list;
    Atom hint = 0;

    for (unsigned int k = 0; k < len; k++) {
      if ((Atom) array[k] == XInternAtom(display, reqHint, True)) {
        hint = list[k];
      }
    }

    return hint;
  }

  return 0;
}

Atom *XWin::haveAtom(Window targetWin, const char *reqProp) {
  Atom a = XInternAtom(display, reqProp, True), actual_type;

  if (a != None) {
    int form;
    unsigned long remain;
    unsigned char *list;
    unsigned long len;

    //	If not disabled, XErrorHandler will interrupt wbar
    if (XGetWindowProperty(display, targetWin, a, 0, 1024, False, XA_ATOM,
                           &actual_type, &form, &len, &remain, &list) !=
        Success) {
      return NULL;
    }

    if (actual_type == None && !form && !remain) {
      return NULL;
    }

    return (Atom *)list;
  }

  return 0;
}

// get icon parameters and data, based on xgeticon by Bertrand Mesot
unsigned char *XWin::windowIcon(Window targetWin, int *iw, int *ih) {
  Atom a = XInternAtom(display, "_NET_WM_ICON", True), actual_type;

  if (a != None) {
    int form;
    unsigned long remain;
    unsigned char *list;
    unsigned char *cardinal32_imagedata;
    unsigned long len;

    //	If not disabled, XErrorHandler will interrupt wbar. Otherwise, remember
    //	to handle the returned NULL
    if (XGetWindowProperty(display, targetWin, a, 0, 1, False, XA_CARDINAL,
                           &actual_type, &form, &len, &remain, &list) !=
        Success) {
      return 0;
    }

    if (!list) {
      return NULL;
    }

    *iw = *(int *)list;

    if (XGetWindowProperty(display, targetWin, a, 1, 1, False, XA_CARDINAL,
                           &actual_type, &form, &len, &remain, &list) !=
        Success) {
      throw _("windowIcon() -- XGetWindowProperty");
      return 0;
    }

    *ih = *(int *)list;

    //	If not disabled, XErrorHandler will interrupt wbar.
    if (XGetWindowProperty(display, targetWin, a, 2, (*iw) * (*ih), False,
                           XA_CARDINAL, &actual_type, &form, &len, &remain,
                           &list) != Success) {
      return 0;
    }

    cardinal32_imagedata = repack64(list, (*iw) * (*ih));
    XFree(list);
    return cardinal32_imagedata;
  }

  return 0;
}

void XWin::windowAction(unsigned long winid) {
  Window window_return;
  Atom a;
  XClientMessageEvent xev;
  int coordx, coordy;
  unsigned char *virtroot, *desknum;
  unsigned long ltemp;
  short toLeft = 0;
  short toUpper = 0;
  virtroot = XWin::windowProp(NULL, "_NET_VIRTUAL_ROOTS", &ltemp);

  if ((XWin::windowProp(NULL, "_NET_NUMBER_OF_DESKTOPS", &ltemp)) &&
      (desknum =
           XWin::windowProp((Window *)(&winid), "_NET_WM_DESKTOP", &ltemp))) {
    a = XInternAtom(display, "_CURRENT_DESKTOP", True);
    xev.type = ClientMessage;
    xev.format = 32;
    xev.data.l[0] = (long) * desknum;
    xev.data.l[1] = CurrentTime;
    XSendEvent(display, XDefaultRootWindow(display), False,
               (SubstructureNotifyMask | SubstructureRedirectMask),
               (XEvent *)&xev);
  }

  if (virtroot) {
    XTranslateCoordinates(display, winid, (Window) * virtroot, 0, 0, &coordx,
                          &coordy, &window_return);
    long *virtDesk = (long *)XWin::windowProp(NULL, "_NET_WORKAREA", &ltemp);
    long *useDesk =
        (long *)XWin::windowProp(NULL, "_NET_DESKTOP_VIEWPORT", &ltemp);

    if (coordx < 0) {
      toLeft = -1;
    }

    if (coordy < 0) {
      toUpper = -1;
    }

    useDesk[0] += coordx / virtDesk[2] * virtDesk[2] + toLeft * virtDesk[2];
    useDesk[1] += coordy / virtDesk[3] * virtDesk[3] + toUpper * virtDesk[3];
    a = XInternAtom(display, "_NET_DESKTOP_VIEWPORT", True);
    xev.type = ClientMessage;
    xev.format = 32;
    xev.data.l[0] = useDesk[0];
    xev.data.l[1] = useDesk[1];
    xev.window = XDefaultRootWindow(display);
    xev.message_type = a;
    XSendEvent(display, XDefaultRootWindow(display), False,
               (SubstructureNotifyMask | SubstructureRedirectMask),
               (XEvent *)&xev);
  }

  a = XInternAtom(display, "_NET_ACTIVE_WINDOW", True);
  xev.message_type = a;
  xev.window = (Window) winid;
  xev.data.l[0] = 0;
  xev.data.l[1] = CurrentTime;
  xev.data.l[2] = 0;
  XSendEvent(display, XDefaultRootWindow(display), False,
             (SubstructureNotifyMask | SubstructureRedirectMask),
             (XEvent *)&xev);
  //KWin needs explicit raise event
  XRaiseWindow(display, (Window) winid);
}

void XWin::windowIconify(unsigned long winid) {
  if (!XWin::issetHint((Window) winid, "_NET_WM_STATE",
                       "__NET_WM_STATE_HIDDEN")) {
    XIconifyWindow(display, (Window) winid, DefaultScreen(display));
  }
}

int XWin::windowFocused() {
  int revert;
  Window win_return;
  XGetInputFocus(display, &win_return, &revert);

  if (win_return == window) {
    return 1;
  }

  return 0;
}

void XWin::selectWindowInput(unsigned long winid) {
  //    eventMask = ev_mask|PropertyChangeMask;
  XSelectInput(display, (Window) winid, PropertyChangeMask);
}

unsigned char *repack64(unsigned char *repack_data, long repack_sz) {
  unsigned char *repack;
  long repack_alloc;
  long orig_idx, repack_idx;

  /*
  * Check if we are dealing with 64-bit arch. *
  * There, a 64 bit long is returned for CARDINAL32 in XGetWindowProperty
  * with its last four bytes padded with zeroes. We need to remove that
  * padding to pass consistent data to imlib2
  */
  if (sizeof(long) == 8) {
    repack_alloc = repack_sz * 8;

    if (!(repack = (unsigned char *)malloc(repack_alloc))) {
      return NULL;
    }

    /*
    * seems somewhat bulky, but doing it in a cycle instead of byte-shift
    * doesn't suffer from endianness incompatibility and doesn't
    * cause potential slowdowns on SMP machines
    */
    for (orig_idx = repack_idx = 0; orig_idx < repack_alloc;) {
      repack[repack_idx] = repack_data[orig_idx];
      repack_idx++;
      orig_idx++;
      repack[repack_idx] = repack_data[orig_idx];
      repack_idx++;
      orig_idx++;
      repack[repack_idx] = repack_data[orig_idx];
      repack_idx++;
      orig_idx++;
      repack[repack_idx] = repack_data[orig_idx];
      repack_idx++;
      orig_idx++;
      /* skip four padding zeroes */
      orig_idx += 4;
    }
  } else /* 32 bit is native for CARDINAL32, return the same data */
      {
    repack_alloc = repack_sz * 4;

    if (!(repack = (unsigned char *)malloc(repack_alloc))) {
      return NULL;
    }

    memmove(repack, repack_data, repack_alloc);
  }

  return repack;
}

Pixmap GetRootPixmap(Display *display) {
  Pixmap currentRootPixmap = None;
  Atom act_type;
  Atom _XROOTPMAP_ID = XInternAtom(display, "_XROOTPMAP_ID", False);
  Atom _XSETROOT_ID = XInternAtom(display, "_XSETROOT_ID", False);
  int act_format;
  unsigned long nitems, bytes_after;
  unsigned char *prop = NULL;

  if (XGetWindowProperty(display, DefaultRootWindow(display), _XROOTPMAP_ID, 0,
                         1, False, XA_PIXMAP, &act_type, &act_format, &nitems,
                         &bytes_after, &prop) == Success) {
    //        puts("GetProp succeeded");
    if (prop) {
      //                puts("prop exists");
      currentRootPixmap = *((Pixmap *)prop);
      XFree(prop);
    }
  }

  if (currentRootPixmap == None &&
      XGetWindowProperty(display, DefaultRootWindow(display), _XSETROOT_ID, 0,
                         1, False, XA_PIXMAP, &act_type, &act_format, &nitems,
                         &bytes_after, &prop) == Success) {
    //        puts("GetProp2 succeeded");
    if (prop) {
      //                puts("prop2 exists");
      currentRootPixmap = *((Pixmap *)prop);
      XFree(prop);
    }
  }

  return currentRootPixmap;
}
